package com.springboot.middleware.middleware_grab_red_envelopes.server.service;

import com.springboot.middleware.middleware_grab_red_envelopes.server.dto.RedPacketDto;
import com.springboot.middleware.middleware_grab_red_envelopes.server.utils.RedPacketUtil;
import com.springboot.middleware.middleware_grab_red_envelopes.server.utils.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 红包业务逻辑处理接口-实现类
 */
@Service
public class RedPacketServiceImpl implements RedPacketService {
    private static final Logger log = LoggerFactory.getLogger(RedPacketServiceImpl.class);
    //存储到Redis时定义的key前缀
    private static final String keyPrefix = "redis:red:packet:";
    //RedisUtil
    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RedService redService;

    /**
     * 发红包核心业务逻辑实现
     *
     * @param dto 存放用户id,红包个数和总金额的dto
     * @return 红包全局唯一标识串
     * @throws Exception
     */
    @Override
    public String handOut(RedPacketDto dto) throws Exception {
        //合法性检验
        if (dto.getTotal() > 0 && dto.getAmount() > 0) {
            List<Integer> list = RedPacketUtil.divideRedPackage(dto.getAmount(), dto.getTotal());
            //生成红包全局唯一标识串
            String timestamp = String.valueOf(System.nanoTime());
            //根据缓存Key的前缀与其他信息拼接成一个心的用于存储随机金额列表的Key
            String redId = new StringBuffer(keyPrefix).append(dto.getUserId()).append(":").append(timestamp).toString();//StringBuffer线程安全
            //将随机金额列表存入缓存List中
            for (Integer i : list) {
                redisUtil.lSet(redId, i);
            }
            //根据缓存key的前缀与其他信息拼接成一个新的用于存储红包总数的key
            String redTotalKey = redId + ":total";
            redisUtil.set(redTotalKey, dto.getTotal());

            //异步记录红包的全局唯一标识串,红包个数与随机金额列表信息至数据库中
            redService.recordRedPacket(dto, redId, list);
            return redId;
        } else {
            throw new Exception("系统异常-分发红包-参数不合法");
        }
    }

    /**
     * 抢红包
     *
     * @param userId 用户id
     * @param redId  红包全局唯一标识串
     * @return 红包金额
     * @throws Exception
     */
    @Override
    public BigDecimal rob(Integer userId, String redId) throws Exception {
        //判断是否已经抢过红包,如果已经抢过,直接返回红包金额
        Object obj = redisUtil.get(redId + userId + ":rob");
        if (obj != null) {
            return new BigDecimal(obj.toString());
        }
        //未抢过红包,进行click抢红包,返回是否抢到
        Boolean res = click(redId);
        //如果抢到红包
        if (res) {
            final String lockKey = redId + userId + "-lock";
            //当多个并发的线程同一时刻调用setIfAbsent时,Redis底层会将线程加入"队列"排队处理
            boolean lock = redisUtil.setIfAbsent(lockKey, redId);
            redisUtil.expire(lockKey, 24L, TimeUnit.HOURS);
            try {
                if (lock) {
                    //抢到的红包金额
                    Object value = redisUtil.lLeftPop(redId);
                    if (value != null) {
                        String redTotalKey = redId + ":total";
                        //更新缓存中剩余的红包数,即红包数-1
                        Integer currTotal = redisUtil.get(redTotalKey) != null ? (Integer) redisUtil.get(redTotalKey) : 0;
                        redisUtil.set(redTotalKey, currTotal - 1);
                        //将红包金额返回前端前,使其单位变为:元
                        BigDecimal result = new BigDecimal(value.toString()).divide(new BigDecimal(100));
                        //将抢到红包用户的帐号信息及抢到的金额记录入数据库
                        redService.recordRobRedPacket(userId, redId, new BigDecimal(value.toString()));
                        //将当前抢到红包的用户设置入缓存,表示该用户已经抢过该红包
                        redisUtil.set(redId+userId+":rob", result, 24L, TimeUnit.HOURS);
                        log.info("当前用户抢到红包了: userId={}, key={}, 金额={} ",userId, redId, result);
                        return result;
                    }
                }
            } catch (Exception e) {
                throw new Exception("系统异常-抢红包-加分布式锁失败");
            }
        }
        return null;
    }

    /**
     * 点红包的业务处理逻辑
     * 如果返回true,代表缓存中还有红包,即剩余个数>0
     * 否则,返回false,表示红包已经抢光
     *
     * @throws Exception
     */
    private Boolean click(String redId) throws Exception {
        String redTotalKey = redId + ":total";
        //从缓存中查询是否有红包
        Object total = redisUtil.get(redTotalKey);
        //判断查询结果是否为空,非空其value是否大于0,非空且大于,表示有红包,返回true
        return total != null && Integer.parseInt(total.toString()) > 0;
    }
}
